29 марта 2024
Блог
Брокер сообщений Rabbit MQ: как определиться с типом очередей
Если вы выбрали RabbitMQ в качестве брокера сообщений, то не пренебрегайте и выбором подходящего типа очереди. Тип, выбранный по умолчанию, может привести к непредсказуемым последствиям.
Начнем с базы
RabbitMQ – это брокер сообщений, который используется для выполнения отложенных или асинхронных задач. Действует это примерно так: есть приложение, которое кладет полученное сообщение в «очередь» - publisher и есть приложение, которое собирает это сообщение – consumer, и, в соответствии с ним, выполняет какое-то действие.
Самый простой пример использования: на сайте пользователь набирает сообщение и нажимает копку «Отправить», которая должна переадресовать текст сообщения на электронную почту компании. Сайт положит это сообщение в брокер, например, в RabbitMQ, а какой-нибудь специальный микросервис заберет его из очереди и отправит на электронную почту в удобное время (асинхронно).
Базовые термины:
- Очередь (Queue) - базовая сущность, очередь необработанных сообщений
- Exchange -точка маршрутизации сообщений, входящие сообщения попадают сначала в exchange.
- Binding - связь между exchange и queue.
- Publisher - внешнее приложение, генерирующее сообщения в очередь
- Consumer - приложение-обработчик, получающий сообщения из очереди
- Message - сообщение, несущее полезную нагрузку. Проходит весь путь от Publisher до Consumer.
Дилемма
Разработчики в своей работе сталкиваются с дилеммой – какой брокер сообщений выбрать. Эта дилемма не нова. Существуют два самых популярных брокера – Kafka и RabbitMQ. О том, что из этого лучше, пишут много и давно. У каждого из них свои особенности и свои кейсы применения, и выбор того или иного варианта зависит от разных факторов. Например, Kafka чаще используют для обмена сообщениями между несколькими проектами, а для обмена внутри одного проекта предпочитают использовать RabbitMQ.
У нас есть опыт работы и с тем, и с другим брокером сообщений. Но больше, чем на половине наших продуктов используется RabbitMQ. И в данной статье мы хотим затронуть дилемму не менее важную.
Часто разработчики, выбирая RabbitMQ, не задумываются о типе очередей, с которым будут работать, выбирая его по умолчанию. После этого есть риск столкнуться с проблемой – обмен сообщениями происходит непредсказуемо, не приводит к ожидаемым результатам, а иногда данные попросту теряются.
Наши специалисты DevOps провели исследование и сделали подробный разбор каждого типа, чем мы и хотим поделиться.
Типы очередей:
- Classic. Классические очереди FIFO. Они быстрые и не требовательны к ресурсам. Их используют в топологиях, где важна высокая доступность сервиса. Из минусов - непродуманный механизм репликации (mirroring), что может приводить к потере данных.
- Quorum. Реплицируемые FIFO-очереди, основанные на RAFT-алгоритме. Их используют для топологий, где очереди существуют продолжительное время и сохранность данных важнее, чем доступность сервиса.
- Stream. Персистентная и реплицируемая структура данных (не совсем очередь 😊), представляющая собой append-only журнал. Могут использовать для доставки сообщения нескольким подписчикам (без необходимости создания отдельной очереди для каждого). После прочтения консьюмером сообщения не удаляются сразу и могут быть перепрочитаны.
CAP-теорема
CAP – это акроним из трех понятий: Consistency (Согласованность), Availability (Доступность) и Partition tolerance (Устойчивость к разделению). Согласно этой теореме, в распределенной системе нельзя одновременно добиться всех этих свойств. В реализации распределенных вычислений возможно обеспечить не более двух.
Рассмотрим каждый из типов очередей в разрезе каждого из этих свойств.
Согласованность
- Classic. В случае данного типа ее нет. По умолчанию классические очереди не реплицируются и сохраняются только на одной ноде кластера. Есть специальный механизм зеркалирования (репликации), НО он объявлен как deprecated, то есть устарел и в новой версии будет удален. Кроме того, зеркалирование нужно настраивать отдельно и работает оно не самым прозрачным образом, что часто приводит к потере данных.
- Quorum. Согласованность достигается за счет RAFT-алгоритма на основе кворума. Если количество «живых» инстансов в кластере недостаточно для уверенности в сохранности данных, то RabbitMQ приостановит обработку сообщений.
- Stream. В stream-очередях также дейстует RAFT-алгоритм. В этом они похожи на кворумные очереди.
Доступность
- Classic. Умеют обрабатывать падения узлов кластера только при настройке механзима зеркалирования. В случае падения лидера очереди новым лидером становится самое старое «зеркало». Есть много особенностей, и восстановление после сбоя требует полной ресинхронизации, что может привести к долгому периоду недоступности очереди.
- Quorum. Спокойно обрабатывают падение узлов кластера, пока сохраняется quorum (большинство). В случае выхода из строя большинства узлов - приостанавливают обработку сообщений. После восстановления узла происходит частичная ресинхронизация (гораздо быстрее).
- Stream. Поведение похоже на quorum-очереди
Устойчивость к разделению
- Classic. Механизм реакции на сетевое разделение настраивается отдельно через параметр cluster_partition_handling и по умолчанию требует вмешательства человека для обработки сбоя. Очень часто приводит к ситуации «Split-Brain» - когда активный и пассивный серверы попытаются взять на себя роль активного сервера, что что может привести к несогласованности данных.
- Quorum. В случае сетевого разделения в рабочем состоянии остается только та часть, в которой сохранилось большинство. Другие части кластера приостанавливают свою работу до восстановления связи.
- Stream. Поведение также похоже на quorum-очереди.
Выводы
Нельзя сказать, что есть вариант лучше или хуже. Все зависит исключительно от ваших потребностей:
- Если вам важна доступность брокера и при этом не так важна сохранность сообщений - используйте классические очереди. Но помните, что механизм зеркалирования объявлен как «deprecated» и будет удален в 4-ой версии RabbitMQ.
- Если сохранность данных важнее доступности брокера - используйте кворумные очереди.
- А если вам нужен функционал подобный функционалу Kafka - присмотритесь к stream.